n<template>
  <div class="vtkContainer"></div>
</template>

<script>
import vtkFullScreenRenderWindow from "vtk.js/Sources/Rendering/Misc/FullScreenRenderWindow";
import vtkXMLPolyDataReader from "vtk.js/Sources/IO/XML/XMLPolyDataReader";
import vtkActor from "vtk.js/Sources/Rendering/Core/Actor";
import vtkMapper from "vtk.js/Sources/Rendering/Core/Mapper";
import vtkOrientationMarkerWidget from "vtk.js/Sources/Interaction/Widgets/OrientationMarkerWidget";
import vtkAxesActor from "vtk.js/Sources/Rendering/Core/AxesActor";
import vtkLandmarkTransform from "vtk.js/Sources/Common/Transform/LandmarkTransform";
import vtkPoints from "vtk.js/Sources/Common/Core/Points";
import vtkMath from "vtk.js/Sources/Common/Core/Math";
import { mat4, mat3, vec3, vec4 } from "gl-matrix";
import { downloadModel } from "services/filehelper";

const JSZip = require("jszip");
const Quaternion = require("quaternion");

export default {
  name: "SCR3RobotArm",
  data() {
    return {
      vtpArray: [
        {
          OriginPoint: [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
          ],
          Matrix: mat4.identity(new Float64Array(16)),
          actor: null,
        },
        {
          OriginPoint: [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
          ],
          Position: [0, 0, 300],
          Normal: [0, 0, 1],
          CurrentPosition: [0, 0, 300],
          CurrentNormal: [0, 0, 1],
          AngleRange: [-180, 180],
          CurrentAngle: this.angle1,
          Matrix: mat4.identity(new Float64Array(16)),
          actor: null,
        },
        {
          OriginPoint: [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
          ],
          Position: [0, 0, 300],
          Normal: [0, 1, 0],
          CurrentPosition: [0, 0, 300],
          CurrentNormal: [0, 1, 0],
          AngleRange: [-120, 120],
          CurrentAngle: this.angle2,
          Matrix: mat4.identity(new Float64Array(16)),
          actor: null,
        },
        {
          OriginPoint: [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
          ],
          Position: [0, 0, 600],
          Normal: [0, 0, 1],
          CurrentPosition: [0, 0, 600],
          CurrentNormal: [0, 0, 1],
          AngleRange: [-180, 180],
          CurrentAngle: this.angle3,
          Matrix: mat4.identity(new Float64Array(16)),
          actor: null,
        },
        {
          OriginPoint: [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
          ],
          Position: [0, 0, 600],
          Normal: [0, 1, 0],
          CurrentPosition: [0, 0, 600],
          CurrentNormal: [0, 1, 0],
          AngleRange: [-120, 120],
          CurrentAngle: this.angle4,
          Matrix: mat4.identity(new Float64Array(16)),
          actor: null,
        },
        {
          OriginPoint: [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
          ],
          Position: [0, 0, 900],
          Normal: [0, 0, 1],
          CurrentPosition: [0, 0, 900],
          CurrentNormal: [0, 0, 1],
          AngleRange: [-180, 180],
          CurrentAngle: this.angle5,
          Matrix: mat4.identity(new Float64Array(16)),
          actor: null,
        },
        {
          OriginPoint: [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
          ],
          Position: [0, 0, 900],
          Normal: [0, 1, 0],
          CurrentPosition: [0, 0, 900],
          CurrentNormal: [0, 1, 0],
          AngleRange: [-120, 120],
          CurrentAngle: this.angle6,
          Matrix: mat4.identity(new Float64Array(16)),
          actor: null,
        },
        {
          OriginPoint: [
            [0, 0, 0],
            [0, 0, 0],
            [0, 0, 0],
          ],
          Position: [0, 0, 1080],
          Normal: [0, 0, 1],
          CurrentPosition: [0, 0, 1080],
          CurrentNormal: [0, 0, 1],
          AngleRange: [-180, 180],
          CurrentAngle: this.angle7,
          Matrix: mat4.identity(new Float64Array(16)),
          actor: null,
        },
      ],
      fullScreenRenderer: null,
      transform: null,
    };
  },
  props: {
    src: {
      type: String,
      required: true,
      default: "",
    },
    angle1: {
      type: Number,
      required: true,
      default: 0,
    },
    angle2: {
      type: Number,
      required: true,
      default: 0,
    },
    angle3: {
      type: Number,
      required: true,
      default: 0,
    },
    angle4: {
      type: Number,
      required: true,
      default: 0,
    },
    angle5: {
      type: Number,
      required: true,
      default: 0,
    },
    angle6: {
      type: Number,
      required: true,
      default: 0,
    },
    angle7: {
      type: Number,
      required: true,
      default: 0,
    },
  },
  watch: {
    angle1: function (newValue, oldValue) {
      this.angleChanged(1, newValue, -180, 180);
    },
    angle2: function (newValue, oldValue) {
      this.angleChanged(2, newValue, -120, 120);
    },
    angle3: function (newValue, oldValue) {
      this.angleChanged(3, newValue, -180, 180);
    },
    angle4: function (newValue, oldValue) {
      this.angleChanged(4, newValue, -120, 120);
    },
    angle5: function (newValue, oldValue) {
      this.angleChanged(5, newValue, -180, 180);
    },
    angle6: function (newValue, oldValue) {
      this.angleChanged(6, newValue, -120, 120);
    },
    angle7: function (newValue, oldValue) {
      this.angleChanged(7, newValue, -180, 180);
    },
  },
  components: {},
  created() {
    this.transform = vtkLandmarkTransform.newInstance();
    this.transform.setMode(vtkLandmarkTransform.Mode.RIGID_BODY);
    const source = vtkPoints.newInstance();
    source.setNumberOfPoints(3);
    this.transform.setSourceLandmark(source);
    const target = vtkPoints.newInstance();
    target.setNumberOfPoints(3);
    this.transform.setTargetLandmark(target);
    this.downloadRobot(this.src).then(() => {
      this.computeParameters();
      this.createViewer();
    });
  },
  methods: {
    // sleep(ms) {
    //   return new Promise((resolve) => setTimeout(resolve, ms));
    // },
    downloadRobot(filename) {
      return downloadModel(filename).then(async (res) => {
        const promises = [];
        await JSZip.loadAsync(res).then(async (zip) => {
          zip.forEach((relativePath, file) => {
            promises.push(
              zip
                .file(relativePath)
                .async("uint8array")
                .then((data) => {
                  const index = parseInt(relativePath.split(".")[0]);
                  const vtpReader = vtkXMLPolyDataReader.newInstance();
                  vtpReader.parseAsArrayBuffer(data);
                  const pointData = vtpReader.getOutputData();
                  const numberOfPoints = pointData.getNumberOfPoints();
                  for (let i = 0; i < 3; ++i) {
                    let point = pointData
                      .getPoints()
                      .getPoint((i * numberOfPoints) / 3);
                    this.vtpArray[index].OriginPoint[i] = point.filter(
                      () => true
                    );
                  }
                  this.vtpArray[index]["data"] = pointData;
                })
            );
          });
          await Promise.all(promises);
        });
      });
    },
    createViewer() {
      this.fullScreenRenderer = vtkFullScreenRenderWindow.newInstance({
        rootContainer: document.getElementsByClassName("vtkContainer")[0],
        containerStyle: {
          height: "100%",
          width: "100%",
          position: "relative",
        },
      });
      const renderer = this.fullScreenRenderer.getRenderer();
      const renderWindow = this.fullScreenRenderer.getRenderWindow();
      this.vtpArray.forEach((vtp) => {
        const mapper = vtkMapper.newInstance();
        const actor = vtkActor.newInstance();
        vtp.actor = actor;
        actor.setMapper(mapper);
        actor.setUserMatrix(vtp.Matrix);
        mapper.setInputData(vtp.data);
        renderer.addActor(actor);
      });

      const axes = vtkAxesActor.newInstance();
      const orientation = vtkOrientationMarkerWidget.newInstance();
      orientation.setActor(axes);
      orientation.setInteractor(renderWindow.getInteractor());
      orientation.setEnabled(true);
      const camera = renderer.getActiveCamera();
      const position = camera.getFocalPoint();
      camera.setPosition(position[0] + 2000, position[1], position[2]);
      camera.setViewUp(0, 0, 1);
      this.resetCamera(renderer);
      renderWindow.render();
    },
    dot_product(vector1, vector2) {
      var result = 0;
      for (var i = 0; i < 3; i++) {
        result += vector1[i] * vector2[i];
      }
      return result;
    },
    resetCamera(renderer) {
      let bounds = renderer.computeVisiblePropBounds();
      const camera = renderer.getActiveCamera();
      const vn = camera.getViewPlaneNormal();
      let center = [0, 0, 0];
      center[0] = (bounds[0] + bounds[1]) / 2.0;
      center[1] = (bounds[2] + bounds[3]) / 2.0;
      center[2] = (bounds[4] + bounds[5]) / 2.0;
      const aspect = [1, 1];
      let width = (bounds[3] - bounds[2]) / aspect[1];
      if ((bounds[5] - bounds[4]) / aspect[1] > width) {
        width = (bounds[5] - bounds[4]) / aspect[1];
      }

      // Check X and Y for the X axis on the window

      if ((bounds[1] - bounds[0]) / aspect[0] > width) {
        width = (bounds[1] - bounds[0]) / aspect[0];
      }

      if ((bounds[3] - bounds[2]) / aspect[0] > width) {
        width = (bounds[3] - bounds[2]) / aspect[0];
      }
      let viewAngle = 30;
      if (camera.getParallelProjection()) {
        viewAngle = 30; // the default in vtkCamera
      } else {
        viewAngle = camera.getViewAngle();
      }

      let distance = width / Math.tan((viewAngle * Math.PI) / 360.0);

      // Check view-up vector against view plane normal

      let vup = camera.getViewUp();
      if (Math.abs(this.dot_product(vup, vn)) > 0.999) {
        camera.setViewUp(-vup[2], vup[0], vup[1]);
      }

      // Update the camera

      camera.setFocalPoint(center[0], center[1], center[2]);
      camera.setPosition(
        center[0] + distance * vn[0],
        center[1] + distance * vn[1],
        center[2] + distance * vn[2]
      );

      // Setup default parallel scale

      camera.setParallelScale((0.5 * width) / 0.85);

      bounds = renderer.computeVisiblePropBounds();
      bounds[0] = bounds[2] = bounds[4];
      bounds[1] = bounds[3] = bounds[5];
      renderer.resetCameraClippingRange(bounds);
    },
    clamp(num, min, max) {
      return num <= min ? min : num >= max ? max : num;
    },
    computeParameters() {
      for (var i = 1; i < this.vtpArray.length; ++i) {
        this.computeParameter(i);
      }
    },
    computeParameter(index) {
      if (index > 1) {
        let out = [0, 0, 0, 0];
        let temp = this.vtpArray[index].Normal.filter(() => true);
        temp.push(1);
        vec4.transformMat4(out, temp, this.vtpArray[index - 1].Matrix);
        for (var i = 0; i < 3; ++i) {
          out[i] -= this.vtpArray[index - 1].Matrix[12 + i];
        }
        vtkMath.normalize(out);
        this.vtpArray[index].CurrentNormal = out.slice(0, 3);
        temp = this.vtpArray[index].Position.filter(() => true);
        temp.push(1);
        vec4.transformMat4(out, temp, this.vtpArray[index - 1].Matrix);
        this.vtpArray[index].CurrentPosition = out.slice(0, 3);
      }
      const source = this.transform.getSourceLandmark();
      const target = this.transform.getTargetLandmark();
      for (var i = 0; i < 3; ++i) {
        let originPoint = this.vtpArray[index].OriginPoint[i].filter(
          () => true
        );
        originPoint.push(1);
        source.setPoint(i, originPoint[0], originPoint[1], originPoint[2]);
        if (index > 1) {
          let temp = [];
          vec4.transformMat4(
            temp,
            originPoint,
            this.vtpArray[index - 1].Matrix
          );
          originPoint = temp.filter(() => true);
        }
        let result = this.rotatePointAroundLine(
          originPoint,
          this.vtpArray[index].CurrentNormal,
          this.vtpArray[index].CurrentPosition,
          this.vtpArray[index].CurrentAngle
        );
        target.setPoint(i, result[0], result[1], result[2]);
      }
      this.transform.update();
      this.vtpArray[index].Matrix = mat4.clone(this.transform.getMatrix());
    },
    rotatePointAroundLine(origin, axis, center, theta) {
      origin = origin.map((item, index) => {
        return item - center[index];
      });
      vtkMath.normalize(axis);
      const q = Quaternion.fromAxisAngle(
        axis,
        vtkMath.radiansFromDegrees(theta)
      );
      let result = q.rotateVector(origin);
      result = result.map((item, index) => {
        return item + center[index];
      });

      return result;
    },
    angleChanged(index, value, min, max) {
      value = this.clamp(value, min, max);
      this.vtpArray[index].CurrentAngle = value;
      this.computeParameters();
      this.renderRobotArm();
    },
    renderRobotArm() {
      this.vtpArray.forEach((vtp) => {
        vtp.actor.setUserMatrix(mat4.clone(vtp.Matrix));
        vtp.actor.modified();
      });
      // this.resetCamera(this.fullScreenRenderer.getRenderer());
      this.fullScreenRenderer.getRenderWindow().render();
    },
  },
};
</script>

<style scoped>
.vtkContainer {
  display: flex;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: space-evenly;
}
</style>
